AsyncTask可以正确,轻松地使用UI线程。 此类允许您执行后台操作并在UI线程上发布结果,而无需操纵Threads或者Handler。
AsyncTask被设计为围绕Thread和Handler的帮助类,并不是一个通用的线程框架。 理想情况下,AsyncTasks应该用于短操作(最多几秒钟)。如果您需要长时间保持线程运行,强烈建议您使用java.util.concurrent包提供的各种API。 例如Executor
,ThreadPoolExecutor
和 FutureTask
。
From Google的官方文档
1、AsyncTask的使用
AsyncTask 用来在后台线程中执行任务,当任务执行完毕之后将结果发送到主线程当中。它有三个重要的泛类型参数,分别是 Params
、Progress
和 Result
,分别用来指定参数、进度和结果的值的类型。 以及四个重要的方法,分别是 onPreExecute()
, doInBackground()
, onProgressUpdate()
和 onPostExecute()
。 这四个方法中,除了 doInBackground()
,其他三个都是运行在UI线程的,分别用来处理在任务开始之前、任务进度改变的时候以及任务执行完毕之后的逻辑,而 doInBackground()
运行在后台线程中,用来执行耗时的任务。
1.1、典型的使用:
1 | public class AsyncTaskActivity extends BaseActivity { |
1.2、非典型使用(并发)
1 | // 替换1.1中的25 Line,因为只有一个url,此处仅是举例,无实际意义 |
使用AsyncTask的时候要注意以下几点内容:
- AsyncTask 的对象必须在主线程中创建;
execute()
方法必须在UI线程中被调用;- 不要直接调用
onPreExecute()
,doInBackground()
,onProgressUpdate()
和onPostExecute()
; - 一个AsyncTask对象的
execute()
方法只能被调用一次;
2、AsyncTask源码分析
2.1、AsyncTask 的初始化过程
当初始化一个 AsyncTask 的时候,所有的重载构造方法都会调用下面的这个构造方法。这里做了几件事情:
- 初始化一个 Handler 对象 mHandler,该 Handler 用来将消息发送到它所在的线程中,通常使用默认的值,即主线程的 Handler;
- 初始化一个 WorkerRunnable 对象 mWorker。它是一个
WorkerRunnable
类型的实例,而WorkerRunnable
又继承自Callable
,因此它是一个可以被执行的对象。我们会把在该对象中回调doInBackground()
来将我们的业务逻辑放在线程池中执行。 - 初始化一个 FutureTask 对象 mFuture。该对象包装了
mWorker
并且当mWorker
执行完毕之后会调用它的postResultIfNotInvoked()
方法来通知主线程(不论任务已经执行完毕还是被取消了,都会调用这个方法)。
1 | public AsyncTask(@Nullable Looper callbackLooper) { |
当这样设置完毕之后,我们就可以使用 execute()
方法来开始执行任务了。
2.2、AsyncTask 中任务的串行执行过程
我们从 execute()
方法开始分析 AsyncTask,
1 |
|
当我们调用 AsyncTask 的 execute()
方法的时候会立即调用它的 executeOnExecutor()
方法。这里传入了两个参数,分别是一个 Executor
和任务的参数 params
。从上面我们可以看出,当直接调用 execute() 方法的时候会使用默认的线程池 sDefaultExecutor
,而当我们指定了线程池之后,会使用我们指定的线程池来执行任务。
在 1 处,会对 AsyncTask 当前的状态进行判断,这就对应了前面说的,一个任务只能被执行一次。在 2 处会调用 onPreExecute()
方法,如果我们覆写了该方法,那么它就会在这个时候被调用。在 3 处的操作是在为 mWorker
赋值,即把调用 execute
方法时传入的参数赋值给了 mWorker
。接下来,会将 mFuture
添加到线程池中执行。
当我们不指定任何线程池的时候使用的 sDefaultExecutor
是一个串行的线程池,它的定义如下:
1 | public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); |
从上面我们可以看出,我们添加到线程池中的任务实际上并没有直接交给线程池来执行,而是对其进行了处理之后才执行的,SerialExecutor 通过内部维护了双端队列ArrayDeque
,每当一个 AsyncTask 调用 execute()
方法的时候都会被放在该队列当中进行排队。如果当前没有正在执行的任务,那么就从队列中取一个任务交给 THREAD_POOL_EXECUTOR
执行;当一个任务执行完毕之后又会调用 scheduleNext()
取下一个任务执行。也就是说,实际上 sDefaultExecutor
在这里只是起了一个任务调度的作用,任务最终还是交给 THREAD_POOL_EXECUTOR
执行的。这里的THREAD_POOL_EXECUTOR
也是一个线程池,它在静态代码块中被初始化:
1 | static { |
2.3、将任务执行的结果发送到其他线程
上面的 WorkerRunnable
中已经用到了 postResult
方法,它用来将任务执行的结果发送给 Handler
:
1 | private Result postResult(Result result) { |
mHandler
会在创建 AsyncTask 的时候初始化。我们可以通过 AsyncTask 的构造方法传入 Handler 和 Looper 来指定该对象所在的线程。当我们没有指定的时候,会使用 AsyncTask 内部的 InternalHandler
创建 Handler
:
1 | private final Handler mHandler; |
3、总结
上面我们梳理了 AsyncTask 的大致过程,我们来梳理下:
创建 AsyncTask是会创建mWorker和mFuture,mFuture是一个Runable类型的对象,最终会在我们执行execute —> executeOnExecutor时,先调用了onPreExecute()方法,然后将mFuture传入执行队列中等待执行。这里存在两个线程池,一个是 SerialExecutor,一个是 THREAD_POOL_EXECUTOR,前者主要用来进行任务调度,即把交给线程的任务放在队列中进行排队执行,而实际上所有的任务都是在后者中执行完成的。在mFuture被执行的时候,会回调mWorker的call方法,call方法里会调用doInBackground方法,获得doInBackground的执行结果后调用postResult方法,postResult内部通过handler切换线程,最终调用我们的finish方法,finish里面会调用onCancelled(result)或者onPostExecute(result);中的一个。这样我们的AsyncTask的一个关键流程就走完了。